// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mobi.omegacentauri.ao.control;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences.Editor;
import android.location.Address;
import android.location.Criteria;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;

import mobi.omegacentauri.ao.R;
import mobi.omegacentauri.ao.units.LatLong;
import mobi.omegacentauri.ao.util.IO;
import mobi.omegacentauri.ao.util.MiscUtil;
import mobi.omegacentauri.ao.util.OsVersions;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Sets the AstronomerModel's (and thus the user's) position using one of the
 * network, GPS or user-set preferences.
 *
 * @author John Taylor
 */
public class LocationController extends AbstractController implements LocationListener {
	// Must match the key in the preferences file.
	private static final String NO_AUTO_LOCATE = "no_auto_locate";
	// Must match the key in the preferences file.
	private static final String FORCE_GPS = "force_gps";
	private static final int MINIMUM_DISTANCE_BEFORE_UPDATE_METRES = 2000;
	private static final int LOCATION_UPDATE_TIME_MILLISECONDS = 600000;
	private static final String TAG = MiscUtil.getTag(LocationController.class);
	private static final float MIN_DIST_TO_SHOW_TOAST_DEGS = 0.01f;

	private Context context;
	private LocationManager locationManager;

	public LocationController(Context context) {
		this.context = context;
		locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
		if (locationManager != null) {
			Log.d(TAG, "Got location Manager");
		} else {
			Log.d(TAG, "Didn't get location manager");
		}
	}

	@Override
	public void start() {
		Log.d(TAG, "LocationController start");
		boolean noAutoLocate = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
				NO_AUTO_LOCATE, false);

		boolean forceGps = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(FORCE_GPS,
				false);

		if (noAutoLocate) {
			Log.d(TAG, "User has elected to set location manually.");
			setLocationFromPrefs();
			Log.d(TAG, "LocationController -start");
			return;
		}

		if (locationManager == null) {
			// TODO(johntaylor): find out under what circumstances this can happen.
			Log.e(TAG, "Location manager was null - using preferences");
			setLocationFromPrefs();
			return;
		}

		Criteria locationCriteria = new Criteria();
		locationCriteria.setAccuracy(forceGps ? Criteria.ACCURACY_FINE : Criteria.ACCURACY_COARSE);
		locationCriteria.setAltitudeRequired(false);
		locationCriteria.setBearingRequired(false);
		locationCriteria.setCostAllowed(true);
		locationCriteria.setSpeedRequired(false);
		locationCriteria.setPowerRequirement(Criteria.POWER_LOW);

		String locationProvider = locationManager.getBestProvider(locationCriteria, true);
		if (locationProvider == null) {
			Log.w(TAG, "No location provider is enabled");

			if (!setLocationFromIP()) {
				Toast.makeText(context, R.string.location_no_auto, Toast.LENGTH_LONG).show();
				setLocationFromPrefs();
			}

			String possiblelocationProvider = locationManager.getBestProvider(locationCriteria, false);
			if (possiblelocationProvider != null) {
				Log.w(TAG, "No possible location provider.");
				return;
			}

			//      if (!OsVersions.isKindle()) {
			//    	  /* Won't bother with this dialog.  But Kindle Fire 1 users should bug Amazon 
			//    	   * about the impossibility of activating a location provider.
			//    	   */
			AlertDialog.Builder alertDialog = getSwitchOnGPSDialog();
			alertDialog.show();
			//      }
			return;
		} else {
			Log.d(TAG, "Got location provider " + locationProvider);
		}

		locationManager.requestLocationUpdates(locationProvider, LOCATION_UPDATE_TIME_MILLISECONDS,
				MINIMUM_DISTANCE_BEFORE_UPDATE_METRES,
				this);

		Location location = locationManager.getLastKnownLocation(locationProvider);
		if (location != null) {
			LatLong myLocation = new LatLong(location.getLatitude(), location.getLongitude());
			setLocationInModel(myLocation, location.getProvider());
		}
		else {
			Log.e(TAG, "No last known location");
			if (!setLocationFromIP()) {
				Toast.makeText(context, R.string.location_no_auto, Toast.LENGTH_LONG).show();
				setLocationFromPrefs();
			}
		}
	}

	private boolean setLocationFromIP() {	  
		String url = "http://api.hostip.info/get_html.php?position=true";
		String data = IO.getURLFile(url);

		try {
			Log.v(TAG, ""+data);

			Pattern p = Pattern.compile("Latitude:\\s+([-.0-9]+)");
			Matcher m = p.matcher(data);
			if (!m.find())
				return false;
			float latitude = Float.parseFloat(m.group(1));
			p = Pattern.compile("Longitude:\\s+([-.0-9]+)");
			m = p.matcher(data);
			if (!m.find())
				return false;
			float longitude = Float.parseFloat(m.group(1));

			Log.v(TAG, "From "+url+" got "+latitude+" "+longitude);

			LatLong myPosition = new LatLong(latitude, longitude);

			setLocationInModel(myPosition, url);
			showLocationToUser(myPosition, url);

			return true;
		}
		catch (NumberFormatException e) {
			return false;
		}
	}

	private void setLocationInModel(LatLong location, String provider) {
		LatLong oldLocation = model.getLocation();
		if (location.distanceFrom(oldLocation) > MIN_DIST_TO_SHOW_TOAST_DEGS) {
			Log.d(TAG, "Informing user of change of location");
			showLocationToUser(location, provider);
		} else {
			Log.d(TAG, "Location not changed sufficiently to tell the user");
		}
		model.setLocation(location);
	}

	private Builder getSwitchOnGPSDialog() {
		AlertDialog.Builder dialog = new AlertDialog.Builder(context);
		dialog.setTitle(R.string.location_offer_to_enable_gps_title);
		dialog.setMessage(R.string.location_offer_to_enable);
		dialog.setPositiveButton(android.R.string.yes, new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				Log.d(TAG, "Sending to editor location prefs page");
				Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
				context.startActivity(intent);
			}
		});
		dialog.setNegativeButton(android.R.string.no, new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				Log.d(TAG, "User doesn't want to enable location.");
				SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
				Editor editor = prefs.edit();
				editor.putBoolean(NO_AUTO_LOCATE, true);
				editor.commit();
				setLocationFromPrefs();
			}
		});
		return dialog;
	}

	private void setLocationFromPrefs() {
		String longitude_s = PreferenceManager.getDefaultSharedPreferences(context)
				.getString("longitude", "");
		String latitude_s = PreferenceManager.getDefaultSharedPreferences(context)
				.getString("latitude", "");

		float longitude = -80, latitude = 40;
		try {
			longitude = Float.parseFloat(longitude_s);
			latitude = Float.parseFloat(latitude_s);
		} catch (NumberFormatException nfe) {
			Log.e(TAG, "Error parsing latitude or longitude preference");
			Toast.makeText(context, R.string.malformed_loc_error, Toast.LENGTH_LONG).show();
		}

		Location location = new Location(context.getString(R.string.preferences));
		location.setLatitude(latitude);
		location.setLongitude(longitude);

		Log.d(TAG, "Latitude " + longitude);
		Log.d(TAG, "Longitude " + latitude);
		LatLong myPosition = new LatLong(latitude, longitude);
		setLocationInModel(myPosition, context.getString(R.string.preferences));
	}

	@Override
	public void stop() {
		Log.d(TAG, "LocationController stop");

		if (locationManager == null) {
			return;
		}
		locationManager.removeUpdates(this);

		Log.d(TAG, "LocationController -stop");
	}

	@Override
	public void onLocationChanged(Location location) {
		Log.d(TAG, "LocationController onLocationChanged");

		if (location == null) {
			Log.e(TAG, "Didn't get location even though onLocationChanged called");
			setLocationFromPrefs();
			return;
		}

		LatLong newLocation = new LatLong(location.getLatitude(), location.getLongitude());

		Log.d(TAG, "Latitude " + newLocation.latitude);
		Log.d(TAG, "Longitude " + newLocation.longitude);
		setLocationInModel(newLocation, location.getProvider());

		// Only need get the location once.
		locationManager.removeUpdates(this);

		Log.d(TAG, "LocationController -onLocationChanged");
	}

	private void showLocationToUser(LatLong location, String provider) {
		// TODO(johntaylor): move this notification to a separate thread)
		Log.d(TAG, "Reverse geocoding location");
		Geocoder geoCoder = new Geocoder(context);
		List<Address> addresses = new ArrayList<Address>();
		String place = "Unknown";
		try {
			addresses = geoCoder.getFromLocation(location.latitude, location.longitude, 1);
		} catch (IOException e) {
			Log.e(TAG, "Unable to reverse geocode location " + location);
		}

		if (addresses == null || addresses.size() == 0) {
			Log.d(TAG, "No addresses returned");
			place = String.format(context.getString(R.string.location_long_lat), location.longitude,
					location.latitude);
		} else {
			place = getSummaryOfPlace(location, addresses.get(0));
		}

		Log.d(TAG, "Location set to " + place);

		String messageTemplate = context.getString(R.string.location_set_auto);
		String message = String.format(messageTemplate, provider, place);
		Toast.makeText(context, message, Toast.LENGTH_LONG).show();
	}

	private String getSummaryOfPlace(LatLong location, Address address) {
		String template = context.getString(R.string.location_long_lat);
		String longLat = String.format(template, location.longitude, location.latitude);
		if (address == null) {
			return longLat;
		}
		String place = null;
		place = address.getLocality();
		if (place == null) {
			place = address.getSubAdminArea();
		}
		if (place == null) {
			place = address.getAdminArea();
		}
		if (place == null) {
			place = longLat;
		}
		return place;
	}

	@Override
	public void onProviderDisabled(String provider) {
		// No action.
	}

	@Override
	public void onProviderEnabled(String provider) {
		// No action.
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		// No action.
	}
}
